#!/usr/bin/perl

## (C) Copyright 2010- Hewlett-Packard Development Company, L.P.
##
## $Id: install_rpm.pl 18018 2012-05-11 03:13:51Z cheng-xin.cai@hp.com $

## On Bright clusters, installing an RPM is a nontrivial task.  It must be
## installed on each headnode manually, then installed in each software
## image (or maybe even only a subset of software images), and then all the
## provisioning nodes must be updated so all their /cm/images directories
## are in sync.  Given an RPM location, this script will install/update the
## RPM on each headnode and all the software images defined.

use strict;
use warnings;

use Data::Dumper;
use File::Basename;
use File::Copy;
use Getopt::Long;
use POSIX;
use Sys::Hostname;

my $bequiet;
my $force;

$| = 1; # Autoflush STDOUT

my $IMAGEDIR = "/cm/images";

sub get_images
{   
    my @images;
    chomp(@images = `ls $IMAGEDIR 2>/dev/null`);

    return wantarray ? @images : join(',', @images);
}

sub trim($)
{
    my $string = shift;

    $string =~ s/^\s+(.*)\s+$/\1/;

    return ($string);
}

##############################################################################
##############################################################################

##
## Main
##
sub main()
{
    my $validated = 0;
    my $rpmfile = $ARGV[-1];
    my $errs;

    my $results = GetOptions
    (
        "force|f" => \$force,
        "help|h"  => sub { help(); exit(0); },
        "quiet|q" => \$bequiet
    );

    if (!$rpmfile)
    {
        print STDERR "An RPM file must be specified.\nTry '" . basename($0) .
                     " --help' for more information.\n";
        exit(1);
    }

    $validated = ValidateRPM($rpmfile);

    exit($validated) if ($validated != 0);

    PrintMsg(""); # Leading blank

    $errs = InstallRPM($rpmfile);

    PrintMsg(""); # Trailing blank

    exit($errs);
}

##
## Install a given RPM on both head nodes and in each software image under
## /cm/images (using chroot-ed environment).
##
## Arguments: RPM filename (fullpath)
##
## Return Value: Number of errors encountered (0 for success)
##
sub InstallRPM($)
{
    my $rpmfile = shift;
    my ($rpmname, $rpminstalled);
    my ($rpmversion, $rpmrelease);
    my ($cmd, $output, $rpmaction);
    my ($otherhn, $hostname, $sshcmd);
    my $errs = 0;

    $hostname = hostname();

    # Need to be on a Bright cluster
    if (cluster_type() != 0)
    {
        print STDERR "Error: this does not appear to be a Bright cluster.\n";
        return (1);
    }

    chomp($rpmname = `rpm -qp --queryformat=\%{NAME} $rpmfile`);
    chomp($rpmversion = `rpm -qp --queryformat \%{VERSION} $rpmfile`);
    chomp($rpmrelease = `rpm -qp --queryformat \%{RELEASE} $rpmfile`);

    chomp($rpminstalled = `rpm -q $rpmname`);

    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
    {
        PrintMsg("RPM name: $rpmname");
        $rpmaction = "rpm -i";
        $rpmaction .= " --force" if ($force);
    }

    else
    {
        my ($installed_version, $installed_release);

        chomp($installed_version = `rpm -q --queryformat \%{VERSION} $rpmname`);
        chomp($installed_release = `rpm -q --queryformat \%{RELEASE} $rpmname`);

        PrintMsg("RPM name: $rpmname");
        PrintMsg("  Installed.... Version: $installed_version\tRelease: $installed_release");

        $rpmaction = "rpm -U";
        $rpmaction .= " --force" if ($force);
    }

    $cmd = "$rpmaction $rpmfile";

    PrintMsg("  New.......... Version: $rpmversion\tRelease: $rpmrelease\n");

    chomp($output = `cmsh -c "device list masternode" | grep MasterNode | awk {'print \$2'}`);

    # Get "other" headnode
    for my $hn (split("\n", $output))
    {
        next if ($hn eq $hostname);
        $otherhn = $hn;
    }

    PrintMsg("Installing $rpmname on local node $hostname...", 1);

    chomp($output = `$cmd 2>&1 > /dev/null`); # Install/upgrade the RPM on the headnode

    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
    {
        print STDERR "error (" . trim($output) . ")\n";
        $errs++;
    }

    else { PrintMsg("done"); }

    PrintMsg("Installing $rpmname on remote node $otherhn...", 1);

    chomp($output = `scp $rpmfile $otherhn:/tmp`);

    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
    {
        print STDERR "error (" . trim($output) . ")\n";
        $errs++;
    }

    else
    {
        $sshcmd = "ssh $otherhn \"$rpmaction /tmp/" . basename($rpmfile) . "\"";
        chomp($output = `$sshcmd 2>&1 > /dev/null`);

        if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
        {
            print STDERR "error (" . trim($output) . ")\n";
            $errs++;
        }

        else { PrintMsg("done"); }
    }

    my @images = get_images();

    PrintMsg("\nImage names: " . join(" ", @images));

    # Install into each image
    for my $img (@images)
    {
        my $chrootcmd = "chroot /cm/images/$img";

        PrintMsg("\n$img:");

        chomp($rpminstalled = `$chrootcmd rpm -q $rpmname`);

        if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
        {
            $rpmaction = "rpm -i";
            $rpmaction .= " --force" if ($force);
        }

        else
        {
            my ($installed_version, $installed_release);

            chomp($installed_version = `$chrootcmd rpm -q --queryformat \%{VERSION} $rpmname`);
            chomp($installed_release = `$chrootcmd rpm -q --queryformat \%{RELEASE} $rpmname`);

            PrintMsg("  Installed.... Version: $installed_version\tRelease: $installed_release");

            $rpmaction = "rpm -U";
            $rpmaction .= " --force" if ($force);
        }

        $cmd = "$rpmaction /tmp/" . basename($rpmfile);

        PrintMsg("  New.......... Version: $rpmversion\tRelease: $rpmrelease\n");
        PrintMsg("Installing $rpmname into image $img...", 1);

        copy($rpmfile, "/cm/images/$img/tmp"); # Move the RPM into the image

        chomp($output = `$chrootcmd $cmd 2>&1 > /dev/null`); # Install/upgrade the RPM in the image

        if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
        {
            print STDERR "error (" . trim($output) . ")\n";
            $errs++;
        }

        else { PrintMsg("done"); }
    }

    chomp($output = `module load cmd; cmsh -c "softwareimage; updateprovisioners"`);
    PrintMsg("\n$output");

    return ($errs);
}

##
## Display a message to STDOUT, if the --quiet option wasn't specified on the
## command line.  A newline is appended to the message when printed.
##
## Arguments: Two - one required, one optional
##              @_[0] - String to print (reqd)
##              @_[1] - Set to 1 to suppress newline (opt)
##
## Return Value: None
##
sub PrintMsg
{
    my $message = shift;
    my $suppressnewline;

    $suppressnewline = shift if (scalar(@_) > 0);

    return if ($bequiet);

    print "$message";
    print "\n" if (!$suppressnewline);
}

##
## Validate that the file passed in on the command line both exists and is
## actually an RPM.
##
## Arguments: One - required
##              @_[0] - Path to RPM
##
## Return Value: 0 - Success
##               1 - RPM doesn't exist
##               2 - File isn't an RPM
##
sub ValidateRPM($)
{
    my $rpmfile = shift;

    if (! -e $rpmfile)
    {
        print STDERR "Error: file '$rpmfile' doesn't exist.\nTry '" .
                     basename($0) . " --help' for more information.\n";
        return (1);
    }

    chomp(my $isrpm = `/usr/bin/file $rpmfile | grep -i rpm`);

    if ($isrpm eq "" || (WIFEXITED($?) && (WEXITSTATUS($?) != 0)))
    {
        print STDERR "Error: file '$rpmfile' doesn't appear to be an RPM.\nTry '" .
                     basename($0) . " --help' for more information.\n";
        return (2);
    }
}

##
## Display script usage
##
sub help()
{
    my $progname = basename($0);

    print <<END

Usage: $progname {--force} [RPMFILE]

 On a Bright cluster, install an RPM pointed to by the command line parameter
 RPMFILE.  The RPM will be installed on both headnodes, and each software
 image defined on the cluster.  If the --force option is specified, the --force
 option will be specified in the underlying RPM commands as well.

END
}

##############################################################################
##############################################################################

main();
